/*
 * nlock.c
 *
 *  Created on: 21/05/2012
 *      Author: Idan
 */
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include "nlock.h"


void lock_init(nlock* l) {

    l = (nlock*)malloc(sizeof(nlock));
        
    assert(l != NULL);
        
	l->head = NULL;
	l->tail = NULL;
	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_ERRORCHECK_NP);
	pthread_mutex_init(&l->global_lock,&attr);
	pthread_mutexattr_destroy(&attr);
	l->number_of_readers = 0;
	l->number_of_writers = 0;
}

static node* nodeInit(lock_type type) {

       node* qElement = (node*)malloc(sizeof(*qElement));
       
       assert(qElement != NULL);
       
       qElement->type = type;
       pthread_cond_init(&(qElement->condition),NULL);
       qElement->readers = type==READ? 1:0;
       qElement->mayWriteFlag = 0;
       qElement->next = NULL;
       qElement->mayWriteId = -1;
       return qElement;
}

void get_read_lock(nlock* l){

	assert(l != NULL);

    node* newNode;
	pthread_mutex_lock(&l->global_lock);

	assert(l->tail != NULL);

	if(!l->head){
			l->head = nodeInit(READ);
			l->tail = l->head;
			assert(l->tail->next == NULL);
	}
	else if(l->tail->type == READ){
		l->tail->readers++;
	}
	else{
		newNode = nodeInit(READ);
		
		assert(l->tail != NULL);
		assert(l->tail->next != NULL);
		
		l->tail->next = newNode;
		l->tail = newNode;
	}

	assert(l != NULL && l->tail != NULL);
	while(l->number_of_writers > 0)
		pthread_cond_wait(&(l->tail->condition),&(l->global_lock));

	l->number_of_readers++;
	pthread_mutex_unlock(&l->global_lock);
}

void get_may_write_lock(nlock *l){

	int is_may_running = 0;
	assert(l != NULL);

    node* newNode;
	pthread_mutex_lock(&l->global_lock);

	assert(l->tail != NULL);

	if(!l->head){
			l->head = nodeInit(READ);
			l->head->mayWriteFlag = 1;
			l->head->readers = 0;
			l->head->mayWriteId = pthread_self();
			l->tail = l->head;
	}
	else if((l->tail->type == READ) && (l->tail->mayWriteFlag == 0)){
		l->tail->mayWriteFlag = 1;
		l->tail->mayWriteId = pthread_self();
	}
	else{
		newNode = nodeInit(READ);
		newNode->readers = 0;
		newNode->mayWriteFlag = 1;
		newNode->mayWriteId = pthread_self();
		is_may_running = 1;
		
		assert(l->tail != NULL);
		
		l->tail->next = newNode;
		l->tail = newNode;
	}

	assert(l != NULL && l->tail != NULL);
	if(is_may_running == 1) {
	    while(l->number_of_writers > 0 || l->number_of_readers > 0)
		    pthread_cond_wait(&(l->tail->condition),&(l->global_lock));
	}
	else {
		while(l->number_of_writers > 0)
			pthread_cond_wait(&(l->tail->condition),&(l->global_lock));
	}

	l->number_of_readers++;
	pthread_mutex_unlock(&l->global_lock);
}

void get_write_lock(nlock* l){

	assert(l != NULL);

    node* newNode;
	pthread_mutex_lock(&l->global_lock);

	if(!l->head){
			l->head = nodeInit(WRITE);
			l->tail = l->head;
	}
	else{

		newNode = nodeInit(WRITE);

		assert(l->tail != NULL);
		
		l->tail->next = newNode;
		l->tail = newNode;
	}

	assert(l != NULL && l->tail != NULL);
	while(l->number_of_readers > 0 || l->number_of_writers > 0)
		pthread_cond_wait(&(l->tail->condition),&(l->global_lock));

	l->number_of_writers++;
	pthread_mutex_unlock(&l->global_lock);
}

void upgrade_may_write_lock(nlock* l){

	assert(l != NULL);

    node* newNode;
	pthread_mutex_lock(&l->global_lock);

	assert(l->head != NULL);
	//assert(l->head->mayWriteId == pthread_self());

	newNode = nodeInit(WRITE);
	
	assert(l->head->next != NULL);
	printf("got to upgrade3.");
	/** couple of problem here */
	newNode->next = l->head->next;
	l->head->next = newNode;
	assert(l->tail != NULL);
	printf("got to upgrade4.");
	/** problem here. probably stuck on condition */
	l->number_of_readers--;
	printf("num of writers: %d, num of readers: %d", l->number_of_writers, l->number_of_readers);
	while(l->number_of_readers > 0 || l->number_of_writers > 0)
		pthread_cond_wait(&(l->tail->condition),&(l->global_lock));
	l->number_of_writers++;
	//l->number_of_readers--;
	printf("got to upgrade5.");
	pthread_mutex_unlock(&l->global_lock);
}

void release_shared_lock(nlock* l){

	assert(l != NULL);

    node* temp;
	pthread_mutex_lock(&l->global_lock);

	assert(l->head != NULL);

	if(l->head->readers > 1){
		l->head->readers--;
	}
	else if(l->head->readers==1){
	
	    assert(l->head->next != NULL);
	
		temp = l->head;
		l->head = l->head->next;
		free(temp);
		//l->number_of_readers--;
	//	assert(l->head != NULL);
		if(l->head != NULL) {
	    	pthread_cond_broadcast(&(l->head->condition));
		}
	}

	l->number_of_readers--;
	pthread_mutex_unlock(&l->global_lock);
}

void release_exclusive_lock(nlock* l){

	assert(l != NULL);

    node* temp;
	pthread_mutex_lock(&l->global_lock);

	assert(l->head != NULL);

	temp = l->head;
	
	//assert(l->head->next != NULL);
	if(l->head->next != NULL) {
	    l->head = l->head->next;
	}
	else {
	    l->head = NULL;
	}

	free(temp);
	l->number_of_writers--;
//	assert(l->head != NULL);
	if(l->head != NULL) {
    	pthread_cond_broadcast(&(l->head->condition));
	}
	pthread_mutex_unlock(&l->global_lock);
}

void lock_destroy(nlock* l) {
	 assert(l != NULL);
     pthread_mutex_destroy(&l->global_lock);
   //  free(l);
}

